package cn.net.springboot.netty.learn;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.ReferenceCountUtil;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;

public class NettyClient {
    public static void main(String[] args) throws Exception {
        /**
         * 与服务端类似 只需要一个工作线程组
         * channel代替socket的角色进行数据传输
         */
        NioEventLoopGroup workgroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workgroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) {
                            socketChannel.pipeline()
                                    // .addLast(new FixedLengthFrameDecoder(5))
                                    // .addLast(new StringEncoder(StandardCharsets.UTF_8))
                                    // .addLast(new StringDecoder(StandardCharsets.UTF_8))
                                    .addLast(new ClientHandler());
                        }
                    });
            ChannelFuture future = b.connect("127.0.0.1", 8888).sync();
            future.channel().closeFuture().sync();
        } finally {
            workgroup.shutdownGracefully().sync();
        }
    }

    static class ClientHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelRegistered ========");
            super.channelRegistered(ctx);
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelUnregistered ========");
            super.channelUnregistered(ctx);
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelActive ========");
            super.channelActive(ctx);
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello server", StandardCharsets.UTF_8));
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelInactive ========");
            super.channelInactive(ctx);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelReadComplete ========");
            super.channelReadComplete(ctx);
            // java.io.IOException: Connection reset by peer
            // 发送主动断开连接: .addListener(ChannelFutureListener.CLOSE);
            // ctx.writeAndFlush(Unpooled.copiedBuffer("hello server", StandardCharsets.UTF_8));
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== userEventTriggered ========");
            super.userEventTriggered(ctx, evt);
        }

        @Override
        public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelWritabilityChanged ========");
            super.channelWritabilityChanged(ctx);
        }

        /**
         * @param ctx 上下文
         * @param msg 与服务端设置的解码器一致（.addLast(new StringDecoder(StandardCharsets.UTF_8))）。如 {@link StringEncoder} =>
         *            java.lang.String, 默认为： {@link ByteBuf}({@link
         *            io.netty.buffer.UnpooledUnsafeDirectByteBuf})
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== channelRead ========");
            try {
                //do something msg
                ByteBuf buf = (ByteBuf) msg;
                byte[] data = new byte[buf.readableBytes()];
                buf.readBytes(data);
                String request = new String(data, StandardCharsets.UTF_8);
                System.out.println("from server: " + request);

            } finally {
                ReferenceCountUtil.release(msg);  //实际上在writer方法后会自动释放
            }
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " client ======== exceptionCaught ========");
            cause.printStackTrace();
            ctx.close();
        }
    }
}

// Connected to the target VM, address: '127.0.0.1:58493', transport: 'socket'
// 2019-05-20 15:08:17.467 client ======== channelRegistered ========
// 2019-05-20 15:08:17.530 client ======== channelActive ========
// 2019-05-20 15:08:17.692 client ======== channelRead ========
// Client 888
// 2019-05-20 15:08:17.693 client ======== channelReadComplete ========
// 2019-05-20 15:08:17.693 client ======== channelReadComplete ========
// 2019-05-20 15:08:17.695 client ======== channelInactive ========
// 2019-05-20 15:08:17.695 client ======== channelUnregistered ========